﻿using System;
using System.Collections.Generic;
using System.IO;

namespace YYBeep
{
    public class WavHelper
    {
        public static Stream GetWavStream(Beep[] beeps)
        {
            int time = 0;
            foreach(var b in beeps)
            {
                time += b.time;
            }
            int wavBodyLength = MyWav._sample * time / 1000 * MyWav._channel;

            var stream = new MemoryStream();
            var h = new byte[MyWav.headSize];
            var tmph = new MyWav(time, Math.Sin);
            tmph.head.CopyTo(h, 0);
            stream.Write(h, 0, h.Length);

            for (int i = 0; i < beeps.Length; i++)
            {
                var b = new MyWav(beeps[i].time, beeps[i].tone);
                var body = b.GetBodyBytes(beeps[i].hz, beeps[i].pan, beeps[i].volume, beeps[i].tone);
                stream.Write(body, 0, body.Length);
            }

            stream.Position = 0;
            return stream;
        }
    }

    public class MyWav
    {
        public byte[] wav { get; private set; }
        public byte[] head { get; private set; }
        public byte[] body { get; private set; }

        public static readonly int _sample = 44100;//采样率
        public static readonly short _bitPerSample = 8;//采样深度
        public static readonly short _channel = 2;//声道数
        public static readonly int headSize = 46;//头的大小，固定为46
        private int _pointNum;  //单个声道产生采样点的个数

        private float[] _values;  //音量，0 左声道，1 右声道
        private float _value = 100;  //音量
        public float value = 1;  //这个是真正的音量，可以通过此字段对声音大小进行控制
        private int _time;
        private Func<double, double> _tone;  //生成音色的函数， vunc 委托返回值是最后一个泛型，前面按照顺序填写参数

        /// <summary>
        /// 该wav文件的数据大小，也就是不包含头
        /// </summary>
        public int Size { get { return wav.Length - headSize; } }

        //=================================

        /// <summary>
        /// 用指定的时间和音色初始化MyWav
        /// </summary>
        /// <param name="millisecond">生成wav的时间,单位：毫秒</param>
        /// <param name="tone">生成wav的音色</param>
        public MyWav(int millisecond, Func<double, double> tone)
        {
            _time = millisecond;
            _tone = tone;
            _pointNum = _sample * millisecond / 1000;
            _values = new float[2] { _value, _value };

            int bodySize = _pointNum * _channel;
            wav = new byte[headSize + bodySize];
            head = new byte[headSize];
            body = new byte[bodySize];

            WriteHeadInfo(bodySize);
        }

        /// <summary>
        /// 获取播放wav的文件留
        /// </summary>
        /// <param name="hz">音高，单位赫兹</param>
        /// <param name="pan">声像，取值范围 -100 ~ 100</param>
        /// <returns></returns>
        public Stream GetWavStream(float hz, float pan)
        {
            CreateWavBytes(hz, pan);
            return new MemoryStream(wav, 0, wav.Length);
        }

        public Stream GetWavStream(float hz, float pan, float volume, Func<double, double> tone)
        {
            if (volume > 1)
                value = 1;
            else if (volume < 0)
                value = 0;
            else
                value = volume;
            _tone = tone;

            CreateWavBytes(hz, pan);
            value = 1;
            return new MemoryStream(wav, 0, wav.Length);
        }

        public Stream GetWavStream(float hz, float pan, Func<double, double> tone)
        {
            _tone = tone;
            return GetWavStream(hz, pan);
        }

        public Stream GetWavStream(IEnumerable<Beep> beeps)
        {
            List<byte> l = new List<byte>(head);
            foreach (Beep bp in beeps)
            {
                l.AddRange(GetBodyBytes(bp.hz, bp.pan, bp.volume, bp.tone));
            }
            var res = l.ToArray();
            //修改头信息中的body大小
            BitConverter.GetBytes(res.Length - headSize).CopyTo(res, headSize - 4);
            return new MemoryStream(res, 0, res.Length);
        }

        public byte[] GetWavBytes(float hz, float pan, System.Func<double, double> tone,float volume=1)
        {
            if (volume > 1)
                value = 1;
            else if (volume < 0)
                value = 0;
            else
                value = volume;

            _tone = tone;
            CreateWavBytes(hz, pan);
            value = 1;
            return wav;
        }

        public byte[] GetBodyBytes(float hz, float pan, float volume, System.Func<double, double> tone)
        {
            if (volume > 1)
                value = 1;
            else if (volume < 0)
                value = 0;
            else
                value = volume;
            _tone = tone;
            CreateBody(hz, pan);
            value = 1;
            return body;
        }


        private void CreateWavBytes(float hz, float pan)
        {
            CreateBody(hz, pan);

            head.CopyTo(wav, 0);
            body.CopyTo(wav, head.Length);
        }

        /// <summary>
        /// 写入头信息，头的固定大小为44字节
        /// </summary>
        /// <param name="bodySize">数据块（data）的大小</param>
        private void WriteHeadInfo(int bodySize)
        {
            byte[] riff = new byte[4] { 82, 73, 70, 70 };  //文档标识
            int totalSize = this.wav.Length - 8;  //文件数据长度，从下一个字段首地址开始到文件末尾的总字节数。该字段的数值加 8 为当前文件的实际长度。
            byte[] wav = new byte[4] { 87, 65, 86, 69 };  //文件格式类型，wav文件此处都为WAVE
            byte[] fmt = new byte[4] { 102, 109, 116, 32 };  //格式快标识、小写字符串"fmt "，小心最后的空格
            int fmtSize = 18;  //格式快长度，也就是fmt这个格式快的长度
            short pcm = 1;  //编码格式代码，常见的 WAV 文件使用 PCM 脉冲编码调制格式,该数值通常为 1。
            short channel = _channel;  //声道，1 为单声道，2 为双声道
            int sample = _sample;  //每秒单个声道的采样数
            int bitePersec = channel * _sample * _bitPerSample / 8;  //数据传输速率，该数值为:声道数×采样频率×每样本的数据位数/8。 播放软件利用此值可以估计缓冲区的大小。
            short blockAlign = (short)(channel * _bitPerSample / 8);  //数据块对其单位。采样帧大小。该数值为:声道数×位数/8。播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区。
            short bitPerSample = _bitPerSample;  //采样位数，存储每个采样值所用的二进制数位数。常见的位数有 4、8、12、16、24、32
            short wiezhi = 0;//占个位置，为了和audition生成的wav做比较，他的头在这里多一个字节。
            byte[] data = new byte[4] { 100, 97, 116, 97 };
            int dataSize = bodySize;

            int p = 0;
            riff.CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(totalSize).CopyTo(head, p);
            p += 4;
            wav.CopyTo(head, p);
            p += 4;
            fmt.CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(fmtSize).CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(pcm).CopyTo(head, p);
            p += 2;
            BitConverter.GetBytes(channel).CopyTo(head, p);
            p += 2;
            BitConverter.GetBytes(sample).CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(bitePersec).CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(blockAlign).CopyTo(head, p);
            p += 2;
            BitConverter.GetBytes(bitPerSample).CopyTo(head, p);
            p += 2;
            BitConverter.GetBytes(wiezhi).CopyTo(head, p);
            p += 2;
            data.CopyTo(head, p);
            p += 4;
            BitConverter.GetBytes(dataSize).CopyTo(head, p);
        }

        private void CreateBody(float hz, float pan)
        {
            _values[0] = _value;
            _values[1] = _value;
            if (pan < -_value)
                pan = -_value;
            else if (pan > _value)
                pan = _value;

            float speedFactor = 1;// (float)Math.Sin(Math.PI / 2 / _value * pan);
            if (pan < 0)
                _values[1] -= Math.Abs(pan * speedFactor);
            else if (pan > 0)
                _values[0] -= Math.Abs(pan * speedFactor);
            
            double a = _sample / hz;//每个 波形周期中拥有的采样点
            double sinFreq = 2 * Math.PI / a;

            Random r = new Random();
            for (int i = 0; i < _pointNum; i++)
            {
                for (int j = 0; j < _values.Length; j++)
                {
                    int index = i * _values.Length + j;
                    body[index] = (byte)(_values[j] * value * _tone((i % _sample) * sinFreq) + 128);
                }
            }
        }
    }

    public class Beep
    {
        public int hz { get; private set; }
        public float pan { get; private set; } // -100 ~ 100
        public float volume { get; private set; }
        public Func<double, double> tone { get; private set; }// 一个 生成波形的函数
        public int time { get; set; }

        public Beep(int hz, float pan, Func<double, double> tone,int ms)
        {
            this.hz = hz;
            this.pan = pan;
            this.volume = 1;
            this.tone = tone;
            time = ms;
        }

        public Beep(int hz, float pan, float volume, Func<double, double> tone,int ms)
        {
            this.hz = hz;
            this.pan = pan;
            this.volume = volume;
            this.tone = tone;
            time = ms;
        }

        public static Beep CreateBeep(float pitch, float pan, float volume, System.Func<double, double> tone,int ms)
        {
            if (pitch > 100)
                pitch = 100;
            else if (pitch < -100)
                pitch = -100;

            double pitchSpan = 1000 + Math.Round(pitch * 10);
            int hz = (int)(80 * Math.Pow(1.002d, pitchSpan));

            return new Beep(hz, pan, volume, tone, ms);
        }

        public static Beep CreateEmptyBeep(int ms)
        {
            return new Beep(0, 0, Math.Sin, ms);
        }
    }

    public class MyMath
    {
        /// <summary>
        /// 三角波函数，周期为2PI
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public static double TriangleWave(double a)
        {
            double result = -2;  //没有结果返回-2，但这是不可能的
            double r = a % (2 * Math.PI);

            if (r == Math.PI + Math.PI / 2)
                result = -1;
            else if (r == Math.PI)
                result = 0;
            else if (r == Math.PI)
                result = 1;
            else if (r == 0)
                result = 0;
            else if (r > Math.PI + Math.PI / 2)
                result = -1 + (r % (Math.PI / 2)) * 2 / Math.PI;
            else if (r > Math.PI)
                result = -(r % (Math.PI / 2)) * 2 / Math.PI;
            else if (r > Math.PI / 2)
                result = 1 - (r % (Math.PI / 2)) * 2 / Math.PI;
            else if (r > 0)
                result = r % (Math.PI / 2) * 2 / Math.PI;

            return result;
        }

        public static double SquareWav(double a)
        {
            double result = -2;  //没有结果返回-2，但这是不可能的
            double r = a % (2 * Math.PI);

            if (r >= Math.PI)
                result = -1;
            else if (r >= 0)
                result = 1;

            return result;
        }
    }
}
